﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using MiniShellFramework;
using System.Drawing;
using System.Diagnostics.Contracts;
using MiniShellFramework.ComTypes;
using System.IO;
using System.Diagnostics;
using System.Reflection;
using NAntMenu.Core;

namespace NAntMenu.ShellContextMenu
{
    [ComVisible(true)]                              // Make this .NET class a COM object (ComVisible is false on assembly level).
    [Guid("2A57BF13-CB2B-4769-B18C-4133153F8D28")]  // Explicitly assign a GUID: easier to reference and to debug.
    [ClassInterface(ClassInterfaceType.None)]       // Only the functions from the COM interfaces should be accessible.
    public sealed class NAntContextMenu : ContextMenuBase, IDisposable
    {
        public const string ProgId = "NAntContextMenu";
        public const string Description = "NAnt Menu";
        private static bool registered;

        public static List<string> FileExtensions = new List<string>() { ".build" };

        private static string ApplicationPath { get; set; }

        private readonly Bitmap menuBitmap;
        private readonly Bitmap helpBitmap;
        private readonly Bitmap aboutBitmap;
        private readonly Bitmap shellBitmap;
        private readonly Bitmap administratorBitmap;
        private static Menu contextMenu;

        private bool IsWindowsVistaOrHigher = false;

        public NAntContextMenu()
        {
            OperatingSystem OS = Environment.OSVersion;
            this.IsWindowsVistaOrHigher = (OS.Platform == PlatformID.Win32NT) && (OS.Version.Major >= 6);

            string dllFilePath = Assembly.GetExecutingAssembly().Location;
            ApplicationPath = Path.GetDirectoryName(dllFilePath);

            Icon nantIcon = new Icon(new Icon(typeof(NAntContextMenu), @"images.nant.ico"), new Size(16, 16));
            menuBitmap = nantIcon.ToBitmap();
            menuBitmap.MakeTransparent(Color.White);

            Icon helpIcon = new Icon(new Icon(typeof(NAntContextMenu), @"images.Help.ico"), new Size(16, 16));
            helpBitmap = helpIcon.ToBitmap();
            helpBitmap.MakeTransparent(Color.White);

            Icon aboutIcon = new Icon(new Icon(typeof(NAntContextMenu), @"images.About.ico"), new Size(16, 16));
            aboutBitmap = aboutIcon.ToBitmap();
            aboutBitmap.MakeTransparent(Color.White);

            Icon shellIcon = new Icon(new Icon(typeof(NAntContextMenu), @"images.shell.ico"), new Size(16, 16));
            shellBitmap = shellIcon.ToBitmap();
            shellBitmap.MakeTransparent(Color.White);

            if (this.IsWindowsVistaOrHigher)
            {
                Icon administratorIcon = new Icon(new Icon(typeof(NAntContextMenu), "images.shield.ico"), new Size(16, 16));
                administratorBitmap = administratorIcon.ToBitmap();
                administratorBitmap.MakeTransparent(Color.White);
            }

            foreach (string fileExtension in FileExtensions)
            {
                RegisterExtension(fileExtension);
            }
        }

        public static void Register()
        {
            if (registered)
                return;

            foreach (string fileExtension in FileExtensions)
            {
                string existingProgId = null;
                if (!RootKey.TryGetProgIdForFileExension(fileExtension, out existingProgId))
                {
                    RootKey.Register(fileExtension, ProgId);
                }
            }

            registered = true;
        }

        public static void Unregister()
        {
            if (!registered)
                return;

            foreach (string FileExtension in FileExtensions)
            {
                RootKey.Unregister(FileExtension);
            }

            registered = false;
        }

        [ComRegisterFunction]
        public static void ComRegisterFunction(Type type)
        {
            Contract.Requires(type != null);

            Register();

            bool fileAssociationExists = false;

            foreach (string fileExtension in FileExtensions)
            {
                string existingProgId;
                if (RootKey.TryGetProgIdForFileExension(fileExtension, out existingProgId))
                {
                    fileAssociationExists = true;
                    ComRegister(type, Description, existingProgId);
                }
            }

            if (!fileAssociationExists)
            {
                ComRegister(type, Description, ProgId);
            }
        }

        [ComUnregisterFunction]
        public static void ComUnregisterFunction(Type type)
        {
            Contract.Requires(type != null);

            //do not unregister file extensions or progid because it may break existing file associations
            //Unregister();
            //ComUnregister(type, Description, ProgId);
        }

        protected override void QueryContextMenuCore(Menu menu, IList<string> filenames)
        {
            if (filenames.Count != 1 || ContainsUnknownExtension(filenames))
                return;

            //TODO: Low: support multiple file selections

            contextMenu = menu.AddSubMenu("NAnt", new BitmapCustomMenuHandler("NAnt", menuBitmap));

            Menu administratorSubMenu = null;
            if (this.IsWindowsVistaOrHigher)
            {
                administratorSubMenu = contextMenu.AddSubMenu("Run as administrator", new BitmapCustomMenuHandler("Run as administrator", administratorBitmap));
            }

            NAntBuildFile nantBuildFile = new NAntBuildFile(filenames[0]);

            foreach (NAntBuildFileTarget target in nantBuildFile.Targets)
            {
                if (administratorSubMenu != null)
                {
                    administratorSubMenu.AddItem(target.Name, target.Description, OnTargetAsAdministrator);
                }

                contextMenu.AddItem(target.Name, target.Description, OnTarget);
            }

            contextMenu.AddItem(new BitmapCustomMenuHandler("&Command Prompt Here", shellBitmap), "Launch a command prompt here", OnCommandPrompt);
            contextMenu.AddItem(new BitmapCustomMenuHandler("&Help", helpBitmap), "View the help documentation for NAnt Menu", OnHelp);
            contextMenu.AddItem(new BitmapCustomMenuHandler("&About NAnt Menu", aboutBitmap), "Show information about NAnt Menu", OnAboutNAntMenu);

        }

        private static void OnTarget(ref InvokeCommandInfo invokeCommandInfo, IList<string> fileNames)
        {
            Contract.Requires(fileNames != null);
            Contract.Requires(fileNames.Count == 1);

            NAntBuildFile nantBuildFile = new NAntBuildFile(fileNames[0]);

            int itemIndex = Int32.Parse(invokeCommandInfo.lpVerb.ToString());

            NAntBuildFileTarget target = nantBuildFile.Targets.ElementAt(itemIndex - 1);

            NAntBuildRunner nantBuildRunner = new NAntBuildRunner(
                Path.GetFullPath(Path.Combine(ApplicationPath, @"nant-0.91\bin\NAnt.exe")),
                fileNames[0]);
            nantBuildRunner.Run(target.Name);
        }

        private static void OnTargetAsAdministrator(ref InvokeCommandInfo invokeCommandInfo, IList<string> fileNames)
        {
            Contract.Requires(fileNames != null);
            Contract.Requires(fileNames.Count == 1);

            NAntBuildFile nantBuildFile = new NAntBuildFile(fileNames[0]);

            int itemIndex = Int32.Parse(invokeCommandInfo.lpVerb.ToString());

            NAntBuildFileTarget target = nantBuildFile.Targets.ElementAt(itemIndex - 1);

            NAntBuildRunner nantBuildRunner = new NAntBuildRunner(
                Path.GetFullPath(Path.Combine(ApplicationPath, @"nant-0.91\bin\NAnt.exe")),
                fileNames[0]);
            nantBuildRunner.RunAsAdministrator(target.Name);
        }

        private static void OnCommandPrompt(ref InvokeCommandInfo invokeCommandInfo, IList<string> fileNames)
        {
            Process process = new Process();
            process.StartInfo = new ProcessStartInfo(
                Path.Combine(Environment.SystemDirectory, "cmd.exe"), 
                string.Format("/K doskey nant=\"{0}\" && doskey nant.exe=\"{0}\"", Path.GetFullPath(Path.Combine(ApplicationPath, @"nant-0.91\bin\NAnt.exe"))));
            process.StartInfo.WorkingDirectory = Path.GetDirectoryName(fileNames[0]);
            process.Start();
        }

        private static void OnAboutNAntMenu(ref InvokeCommandInfo invokeCommandInfo, IList<string> fileNames)
        {
            AboutDialog aboutDialog = new AboutDialog();
            aboutDialog.ShowDialog();
        }

        private static void OnHelp(ref InvokeCommandInfo invokeCommandInfo, IList<string> fileNames)
        {
            Process.Start(Path.Combine(ApplicationPath, "NAntMenu.chm"));
        }

        public void Dispose()
        {
            Debug.WriteLine("{0}.Dispose", this);
            menuBitmap.Dispose();
        }
    }
}
